import pandas as pd
from joblib import dump, load
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, classification_report
from imblearn.over_sampling import RandomOverSampler
import plotly.express as px
import plotly.graph_objs as go
import plotly.offline as pyo
pyo.init_notebook_mode()
Sebelum masuk pada tahap modelling, kita perlu melakukan preprocessing terlebih dahulu untuk mempersiapkan data yang akan digunakan.
# Load joblib file
df = load('joblib/df.joblib')
df_predict = load('joblib/df_pred.joblib')
# Split label and features
X = df.drop('Attrition', axis=1)
y = df['Attrition']
# Visualisasi Label
count = y.map({0: 'False', 1: 'True'}).value_counts()
fig = px.bar(count, x=count.index, y=count.values,
title='Attrition', color=count.index)
fig.update_layout(height=400, width=500)
fig.show()
Attrition dengan nilai 0(False) bejumlah sangat rendah dibandingkan dengan nilai 1(True). Hal ini disebut dengan imbalanced data. Untuk mengatasi hal ini, kita dapat melakukan oversampling pada data Attrition dengan nilai 1(True) agar jumlahnya sama dengan data Attrition dengan nilai 0(False). Jika tidak dilakukan, maka model yang dibuat akan cenderung memprediksi nilai 0(False) karena jumlahnya yang lebih banyak.# Oversampling
oversample = RandomOverSampler(sampling_strategy='minority', random_state=23)
X, y = oversample.fit_resample(X, y)
# Pasca Oversampling
count = y.map({0: 'False', 1: 'True'}).value_counts()
fig = px.bar(count, x=count.index, y=count.values,
color=count.index, title='Attrition')
fig.update_layout(height=400, width=500)
fig.show()
# Memisahkan Training dan Testing
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=23)
print('Shape of X_train:', X_train.shape)
print('Shape of X_test:', X_test.shape)
Shape of X_train: (1230, 30) Shape of X_test: (528, 30)
Dalam contoh ini perbandingan data training dan testing adalah 70:30. Coba ubah parameter test_size menjadi nilai lain dan lihat perbedaan hasil modelnya.
Note: Inisiasi nilai
random_statebertujuan untuk menghasilkan nilai yang sama setiap kali kita melakukan proses training dan testing. Jika tidak dilakukan, maka nilai yang dihasilkan akan berbeda setiap kali kita melakukan proses training dan testing. Inisiasi ini dilakukan untuk memastikan bahwa hasil model yang kita buat dapat direproduksi.
Pada tahap analisis telah diketahui bahwa terdapat beberapa kolom kategorikal. Agar data dapat diproses oleh model, maka kita perlu melakukan encoding terhadap kolom kategorikal tersebut dengan mengubah data menjadi numerik. Selain itu, kita juga perlu melakukan standardisasi terhadap data numerik agar data yang memiliki skala yang berbeda dapat diproses dengan baik oleh model.
# Menentukan variabel numerik dan kategorikal
num_var = df.select_dtypes(include=['number']).columns[1:].tolist()
cat_var = df.select_dtypes(include=['object']).columns.tolist()
# Preprocessor
preprocessor = ColumnTransformer(
transformers=[
('numeric', StandardScaler(), num_var),
('category', OneHotEncoder(drop='first'), cat_var)
]
)
# Fit dan transform training data
X_train_preprocessed = preprocessor.fit_transform(X_train)
# Transform testing data
X_test_preprocessed = preprocessor.transform(X_test)
# Mengambil nama kolom dari variabel one-hot encoded
cat_var_ohe = preprocessor.named_transformers_[
'category'].get_feature_names_out(cat_var)
cat_var_ohe = cat_var_ohe.tolist()
# Menggabungkan nama kolom numerik dan kategorikal
feature_names = num_var + cat_var_ohe
Algoritma yang digunakan dalam project ini adalah Random Forest Classifier dan logistic regression. Kedua algoritma ini merupakan algoritma yang digunakan untuk melakukan klasifikasi. Kedua algoritma ini memiliki kelebihan dan kekurangan masing-masing. Untuk mengetahui algoritma mana yang lebih baik, kita perlu membandingkan hasil dari kedua algoritma tersebut.
lr_clf = LogisticRegression()
rf_clf = RandomForestClassifier()
# Daftar parameter untuk Logistic Regression
lr_param = {
'C': [0.1, 1, 3, 5],
'max_iter': [100, 150, 200, 250],
'solver': ['newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga'],
}
# Daftar parameter untuk Random Forest
rf_param = {
'n_estimators': [100, 500, 1000],
'max_depth': [10, 20, 30, 50],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4],
}
GridSearchCV digunakan untuk mencari kombinasi parameter terbaik dari kedua algoritma tersebut. Parameter terbaik yang ditemukan akan digunakan untuk membuat model akhir.
Coba ubah nilai daftar parameter dan lihat perbedaan hasil modelnya.
# Grid Search
lr_random_search = GridSearchCV(lr_clf, lr_param, n_jobs=-1, verbose=1, scoring='accuracy')
rf_random_search = GridSearchCV(rf_clf, rf_param, n_jobs=-1, verbose=1, scoring='accuracy')
# Fitting model
lr_model = lr_random_search.fit(X_train_preprocessed, y_train)
rf_model = rf_random_search.fit(X_train_preprocessed, y_train)
Fitting 5 folds for each of 80 candidates, totalling 400 fits Fitting 5 folds for each of 108 candidates, totalling 540 fits
# Menampilkan score kombinasi parameter terbaik dan parameternya
lr_best_params = lr_random_search.best_params_
lr_best_score = lr_random_search.best_score_
print('Best Logistic Regression params:', lr_best_params)
print('Best Logistic Regression score:', lr_best_score)
# Membuat model menggunakan parameter terbaik
lr_model = LogisticRegression(**lr_best_params)
# Fitting model
lr_model.fit(X_train_preprocessed, y_train)
Best Logistic Regression params: {'C': 1, 'max_iter': 100, 'solver': 'liblinear'}
Best Logistic Regression score: 0.7658536585365854
LogisticRegression(C=1, solver='liblinear')In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
LogisticRegression(C=1, solver='liblinear')
# Logistic regression evaluation
# Accuracy score
print('Logistic Regression accuracy score:',
lr_model.score(X_test_preprocessed, y_test))
# Confusion matrix
y_pred = lr_model.predict(X_test_preprocessed)
cm = confusion_matrix(y_test, y_pred)
cm_df = pd.DataFrame(cm, index=['False', 'True'], columns=['False', 'True'])
fig = px.imshow(cm_df, color_continuous_scale='Viridis',
text_auto=True, title='Confusion Matrix')
fig.update_layout(title='Confusion Matrix', width=500, height=500)
fig.show()
# Classification report
print(classification_report(y_test, y_pred))
Logistic Regression accuracy score: 0.7670454545454546
precision recall f1-score support
0 0.81 0.75 0.78 286
1 0.73 0.79 0.76 242
accuracy 0.77 528
macro avg 0.77 0.77 0.77 528
weighted avg 0.77 0.77 0.77 528
# Menampilkan score kombinasi parameter terbaik dan parameternya
rf_best_params = rf_random_search.best_params_
rf_best_score = rf_random_search.best_score_
print('Best Random Forest params:', rf_best_params)
print('Best Random Forest score:', rf_best_score)
# Membuat model menggunakan parameter terbaik
rf_model = RandomForestClassifier(**rf_best_params)
# Fitting model
rf_model.fit(X_train_preprocessed, y_train)
Best Random Forest params: {'max_depth': 20, 'min_samples_leaf': 1, 'min_samples_split': 2, 'n_estimators': 100}
Best Random Forest score: 0.9528455284552845
RandomForestClassifier(max_depth=20)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
RandomForestClassifier(max_depth=20)
# Random forest evaluation
# Accuracy score
print('Random Forest Accuracy score:',
rf_model.score(X_test_preprocessed, y_test))
# Heatmap
y_pred = rf_model.predict(X_test_preprocessed)
cm = confusion_matrix(y_test, y_pred)
cm_df = pd.DataFrame(cm, index=['False', 'True'], columns=['False', 'True'])
fig = px.imshow(cm_df, color_continuous_scale='Viridis',
text_auto=True, title='Confusion Matrix')
fig.update_layout(title='Confusion Matrix', width=500, height=500)
fig.show()
# Classification report
print(classification_report(y_test, y_pred))
Random Forest Accuracy score: 0.9696969696969697
precision recall f1-score support
0 0.98 0.96 0.97 286
1 0.96 0.98 0.97 242
accuracy 0.97 528
macro avg 0.97 0.97 0.97 528
weighted avg 0.97 0.97 0.97 528
# Get the train and test scores for logistic regression and random forest
lr_train_score = lr_model.score(X_train_preprocessed, y_train)
lr_test_score = lr_model.score(X_test_preprocessed, y_test)
rf_train_score = rf_model.score(X_train_preprocessed, y_train)
rf_test_score = rf_model.score(X_test_preprocessed, y_test)
# Create a horizontal grouped bar chart using Plotly
fig = go.Figure(data=[go.Bar(y=['Logistic Regression', 'Random Forest'],
x=[lr_test_score*100, rf_test_score*100],
text=[f'Test: {lr_test_score*100:.2f}%',
f'Test: {rf_test_score*100:.2f}%'],
textposition='auto',
name='Test',
orientation='h',
width=0.3),
go.Bar(y=['Logistic Regression', 'Random Forest'],
x=[lr_train_score*100, rf_train_score*100],
text=[f'Train: {lr_train_score*100:.2f}%',
f'Train: {rf_train_score*100:.2f}%'],
textposition='auto',
name='Train',
orientation='h',
width=0.3)])
# Update the layout
fig.update_layout(title='Model Evaluation',
xaxis=dict(title='Accuracy (%)'),
yaxis=dict(title='Model'),
barmode='group',
width=800,
height=400)
# Show the plot
fig.show()
Random Forest Classifier memiliki nilai akurasi yang lebih baik dibandingkan dengan model yang dibuat dengan menggunakan algoritma logistic regression. Oleh karena itu, model yang akan digunakan adalah model yang dibuat dengan menggunakan algoritma Random Forest Classifier.Untuk penjelasan lebih detail mengenai confusion matrix, recall, dan precision silahkan kunjungi link ini.
# Plot the feature importance
feature_importance = rf_model.feature_importances_
feature_importance = pd.DataFrame(
feature_importance, index=feature_names, columns=['importance'])
fig = px.bar(feature_importance.sort_values(by='importance', ascending=True)[
-10:], x='importance', y=feature_importance.sort_values(by='importance', ascending=True)[-10:].index, orientation='h')
fig.update_layout(title='Feature Importance', xaxis_title='Importance',
yaxis_title='Feature', width=800, height=600)
fig.show()
Attrition berdasarkan Random Forest feature importance adalah MonthlyIncome. Beradasarkan nilai korelasi, maka dapat dikatakan bahwa semakin tinggi MonthlyIncome maka semakin kecil kemungkinan karyawan tersebut akan keluar dari perusahaan.Age. Semakin tinggi usia karyawan maka semakin kecil kemungkinan karyawan tersebut akan keluar dari perusahaan.OverTime juga memiliki pengaruh yang cukup besar terhadap Attrition.# save model
dump(lr_model, 'joblib/lr_model.joblib')
dump(rf_model, 'joblib/rf_model.joblib')
['joblib/rf_model.joblib']
df_cols = df.columns[1:]
df_predict_cols = df_predict.columns
# Cek urutan kolom df dan df_predict
for i, col in enumerate(df_cols):
if col != df_predict_cols[i]:
print(
f"Column order mismatch: {col} in df is not the same as {df_predict_cols[i]} in df_predict")
break
else:
print("Column order is the same")
Column order is the same
# Memprediksi df_predict
## Preprocess df_predict
df_predict_preprocessed = preprocessor.transform(df_predict)
## Predict
y_pred = rf_model.predict(df_predict_preprocessed)
## Create a dataframe
df_predict['Attrition'] = y_pred
df_predict.head()
| Age | BusinessTravel | DailyRate | Department | DistanceFromHome | Education | EducationField | EnvironmentSatisfaction | Gender | HourlyRate | ... | RelationshipSatisfaction | StockOptionLevel | TotalWorkingYears | TrainingTimesLastYear | WorkLifeBalance | YearsAtCompany | YearsInCurrentRole | YearsSinceLastPromotion | YearsWithCurrManager | Attrition | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 38 | Travel_Frequently | 1444 | Human Resources | 1 | 4 | Other | 4 | Male | 88 | ... | 2 | 1 | 7 | 2 | 3 | 6 | 2 | 1 | 2 | 0 |
| 4 | 40 | Travel_Rarely | 1194 | Research & Development | 2 | 4 | Medical | 3 | Female | 98 | ... | 2 | 3 | 20 | 2 | 3 | 5 | 3 | 0 | 2 | 0 |
| 5 | 29 | Travel_Rarely | 352 | Human Resources | 6 | 1 | Medical | 4 | Male | 87 | ... | 4 | 0 | 1 | 3 | 3 | 1 | 0 | 0 | 0 | 0 |
| 12 | 47 | Travel_Rarely | 571 | Sales | 14 | 3 | Medical | 3 | Female | 78 | ... | 3 | 1 | 11 | 4 | 2 | 5 | 4 | 1 | 2 | 0 |
| 18 | 25 | Travel_Frequently | 772 | Research & Development | 2 | 1 | Life Sciences | 4 | Male | 77 | ... | 3 | 2 | 7 | 6 | 3 | 7 | 7 | 0 | 7 | 0 |
5 rows × 31 columns
# combine df and df_predict
df_combined = pd.concat([df, df_predict], ignore_index=True)
df_combined.info()
# save csv
df_combined.to_csv('data/final_data.csv', index=False)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 1470 entries, 0 to 1469 Data columns (total 31 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Attrition 1470 non-null int32 1 Age 1470 non-null int64 2 BusinessTravel 1470 non-null object 3 DailyRate 1470 non-null int64 4 Department 1470 non-null object 5 DistanceFromHome 1470 non-null int64 6 Education 1470 non-null int64 7 EducationField 1470 non-null object 8 EnvironmentSatisfaction 1470 non-null int64 9 Gender 1470 non-null object 10 HourlyRate 1470 non-null int64 11 JobInvolvement 1470 non-null int64 12 JobLevel 1470 non-null int64 13 JobRole 1470 non-null object 14 JobSatisfaction 1470 non-null int64 15 MaritalStatus 1470 non-null object 16 MonthlyIncome 1470 non-null int64 17 MonthlyRate 1470 non-null int64 18 NumCompaniesWorked 1470 non-null int64 19 OverTime 1470 non-null object 20 PercentSalaryHike 1470 non-null int64 21 PerformanceRating 1470 non-null int64 22 RelationshipSatisfaction 1470 non-null int64 23 StockOptionLevel 1470 non-null int64 24 TotalWorkingYears 1470 non-null float64 25 TrainingTimesLastYear 1470 non-null int64 26 WorkLifeBalance 1470 non-null int64 27 YearsAtCompany 1470 non-null float64 28 YearsInCurrentRole 1470 non-null float64 29 YearsSinceLastPromotion 1470 non-null float64 30 YearsWithCurrManager 1470 non-null float64 dtypes: float64(5), int32(1), int64(18), object(7) memory usage: 350.4+ KB
Attrition adalah MonthlyIncome, Age, dan OverTime.Untuk mengurangi jumlah karyawan yang keluar dari perusahaan, maka perusahaan dapat melakukan beberapa hal berikut: